package algoritmos.numericos.tipos;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;

import javax.swing.JButton;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

import Jama.Matrix;
import algoritmos.Error;
import algoritmos.Matriz;
import algoritmos.numericos.Supervisado;

/**
 * Clase que desarrolla el algoritmo KMedias.
 * @author Alberto Milán
 *
 */
public class KMedias extends Supervisado implements Runnable{
	
	/**
	 * Valor umbral para la convergencia.
	 */
	private double epsilon = 1.0E-4;
	
	/**
	 * Medias almacenadas de la iteración previa para comprobación posterior.
	 */
	private ArrayList<Matrix> mediasIteracionAnterior;
	
	/**
	 * Lista de porcentaje de pertenencia de cada dato a cada clase.
	 */
	private ArrayList<ArrayList<Double>> pertenencia;
	
	/**
	 * Número de clases de la distribución
	 */
	private int numClases;
	
	/**
	 * Constructor de la clase
	 */
	public KMedias(JTextArea arg0, JProgressBar arg1, JButton arg2, JButton arg3, JButton arg4){
		super();
		texto = arg0;
		progreso = arg1;
		botonNuevoDato = arg2;
		botonAprendizaje = arg3;
		botonVisualizaGraficos = arg4;
	}
	
	@Override
	protected void aprendizaje(){
		inicializaComponentesAprendizaje();
		if (numClases == 0)
			Error.emiteError("Número de clases a distribuir igual a 0");
		// Inicializa los centros
		for (int i = 0; i < numClases; i++){
			Matriz m = new Matriz(1, entrada.get(0).getColumnDimension());
			centros.add(m);
			conjuntos.add(new HashSet<Integer>());
		}
		// Inicializa la lista de pertenencia con valores aleatorios.
		// Posee un buen comportamiento y la suma de todas las probabilidades
		// siempre es 1.
		pertenencia = new ArrayList<ArrayList<Double>>(entrada.size());
		for (int i = 0; i < entrada.size(); i++){
			ArrayList<Double> entradas = new ArrayList<Double>();
			double resto = 1.0;
			for (int j = 0; j < centros.size(); j++){
				double temp = Math.random() * resto;
				if (j == centros.size() - 1) temp = resto;
				entradas.add(temp);
				resto -= temp;
			}
			pertenencia.add(entradas);
		}		
		iteraciones = 0;
		CalculaMedias();
		CalculaPertenencias();
		iteraciones++;
		while (!convergencia() || iteraciones >= MaximasIteraciones){
			CalculaMedias();
			CalculaPertenencias();
			iteraciones++;
		}
		/*
		 * Completa los conjuntos de pertenencia a clases
		 */
		int contadorDato = 0;
		for (ArrayList<Double> dato : pertenencia){
			int clase = 0;
			double maximo = 0;
			int contadorClase = 0; 
			for (Double p : dato){
				if (p > maximo){
					maximo = p;
					clase = contadorClase;
				}
				contadorClase++;
			}
			conjuntos.get(clase).add(contadorDato);
			contadorDato++;
		}
		/*
		 * Finaliza la ejecución
		 */
		String mensaje = new String();
		long milis = new Date().getTime() - startTime.getTime();
		mensaje += "Proceso finalizado.\n";
		mensaje += "Se han dado " + iteraciones + " iteraciones.\n";
		mensaje += "El proceso ha durado ";
		if (milis > 1000){ // Si ha tardado más de un segundo
			mensaje += (milis / 1000) + " segundos y " + milis % 1000 + " ms.\n";
		} else { // Si ha tardado milisegundos
			mensaje += milis + " ms.\n";
		}
		mensaje += "Centros de las clases:\n";
		for (int i = 0; i < numeroDeClases(); i++){
			mensaje += "Clase " + (i+1) + ": " + centros.get(i) + "\n";
		}
		final String text = mensaje;
		SwingUtilities.invokeLater(new Runnable(){
			@Override
			public void run() {
				texto.append(text);
			}
		});
		finalizaComponentesAprendizaje();
	}
	
	/**
	 * Calcula las nuevas medias respecto a los grados de pertenencia.
	 */
	private void CalculaMedias(){
		// Salva las medias
		mediasIteracionAnterior = centros;
		int iNumClases = centros.size();
		centros = new ArrayList<Matrix>();
		// Recalculo una a una todas las medias
		for (int i = 0; i < iNumClases; i++){
			Matriz numerador = new Matriz(1,dimension);
			double denominador = 0;
			for (int j = 0; j < entrada.size(); j++){
				double temp = Math.pow(pertenencia.get(j).get(i), 2);
				Matriz temp1 = (Matriz) entrada.get(j).clone();
				temp1.timesEquals(temp); 
				numerador.plusEquals(temp1);
				denominador += temp;
			}
			Matriz media = new Matriz(numerador.divide(denominador));
			centros.add(media);
		}		
	}
	
	/**
	 * Calcula los grados de pertenencia según las distancias entre las
	 * medias y los datos.
	 */
	private void CalculaPertenencias(){
		pertenencia = new ArrayList<ArrayList<Double>>();
		// Calculo el denominador común a todas
		double numerador = 0;
		for (int i = 0; i < entrada.size(); i++){
			ArrayList<Double> p = new ArrayList<Double>();
			Matriz dato = (Matriz) entrada.get(i);
			double denominador = 0;
			for (int j = 0; j < centros.size(); j++){
				denominador += (1.0 / dato.distancia((Matriz) centros.get(j)));
			}
			for (int j = 0; j < centros.size(); j++){
				numerador = (1.0 / dato.distancia((Matriz) centros.get(j)));
				p.add(numerador / denominador);
			}
			pertenencia.add(p);
		}
	}

	@Override
	protected boolean convergencia() {
		for (int i = 0; i < centros.size(); i++){
			if (((Matriz)centros.get(i)).distancia((Matriz)mediasIteracionAnterior.get(i)) > epsilon)
				return false;
		}
		return true;
	}

	@Override
	public void perteneceAClase(ArrayList<Number> lista) {
		Matriz dato = new Matriz(lista);
		ArrayList<Double> grados = new ArrayList<Double>();
		for (Matrix m : centros){
			Matriz centro = (Matriz)m;
			double grado = 0;
			grado += dato.distancia(centro);
			grados.add(grado);
		}
		texto.append("\nGrados/Porcentajes de pertenencia a cada clase:\n");
		int contador = 0;
		int clase = 0;
		Double minimo = Double.MAX_VALUE;
		for (Double g : grados){
			contador++;
			if (g < minimo){
				minimo = g;
				clase = contador;
			}
			texto.append("Clase " + contador + 
						  " : Distancia = " + g + "\n");
		}
		texto.append("El dato " + dato + 
				  " pertenece a la clase " + clase + "\n");
	}
	
	/*=========================================================================*
	 *							Accesores y mutadores                          * 
	 *=========================================================================*/	
	
	public int getNumClases() {
		return numClases;
	}
	public void setNumClases(int numClases) {
		this.numClases = numClases;
	}
	
	public double getEpsilon() {
		return epsilon;
	}

	public void setEpsilon(double epsilon) {
		this.epsilon = epsilon;
	}

}
